Pytorch基础知识,包括张量的使用,和自动微分。
Pytorch简介
Torch是一个有大量机器学习算法支持的科学计算框架,是一个与Numpy类似的张量(Tensor) 操作库,其特点是特别灵活,但因其采用了小众的编程语言是Lua,所以流行度不高,这也就有了PyTorch的出现。所以其实Torch是 PyTorch的前身,它们的底层语言相同,只是使用了不同的上层包装语言。
PyTorch是一个基于Torch的Python开源机器学习库,用于自然语言处理等应用程序。它主要由Facebookd的人工智能小组开发,不仅能够 实现强大的GPU加速,同时还支持动态神经网络 ,这一点是现在很多主流框架如TensorFlow都不支持的。 PyTorch提供了两个高级功能:
具有强大的GPU加速的张量计算(如Numpy)
包含自动求导系统的深度神经网络
主要定位两类人群:
NumPy 的替代品,可以利用 GPU 的性能进行计算。
深度学习研究平台拥有足够的灵活性和速度
支持GPU
灵活,支持动态神经网络
TensorFlow和Caffe都是命令式的编程语言,而且是静态的,首先必须构建一个神经网络,然后一次又一次使用相同的结构,如果想要改变网络的结构,就必须从头开始。
但是对于PyTorch,通过反向求导技术,可以让你零延迟地任意改变神经网络 的行为,而且其实现速度快。正是这一灵活性是PyTorch对比TensorFlow的最大优势。
底层代码易于理解
命令式体验
自定义扩展
对比TensorFlow,有些处于劣势,目前PyTorch
还不支持快速傅里叶、沿维翻转张量和检查无穷与非数值张量;
针对移动端、嵌入式部署以及高性能服务器端的部署其性能表现有待提升;
其次因为这个框 架较新,使得他的社区没有那么强大,在文档方面其C库大多数没有文档。
安装和使用 官网 选择对应cuda版本下载即可
1 2 from __future__ import print_function import torch
好的!张量(Tensors)是PyTorch的核心数据结构,类似于NumPy中的多维数组(ndarray),但张量支持GPU加速计算,因此在深度学习中非常高效。下面我将详细展开讲解张量的创建、操作以及一些常用功能。
1. 什么是张量? 张量是一个多维数组,可以表示标量、向量、矩阵以及更高维的数据结构:
0维张量 :标量(Scalar),例如 5
1维张量 :向量(Vector),例如 [1, 2, 3]
2维张量 :矩阵(Matrix),例如 [[1, 2], [3, 4]]
3维及以上张量 :高维数组,例如图像数据通常是3维张量(通道×高度×宽度)。
PyTorch中的张量支持高效的数学运算,并且可以自动求导,是构建神经网络的基础。
1. 张量的「信息区」和「存储区」 (1) 存储区(Storage)
物理内存 :存储区是张量实际数据所在的连续内存块 。
共享性 :多个张量可以共享同一存储区(通过视图操作,如 view()、slice() 等)。
示例 :1 2 3 4 x = torch.arange(1 , 9 ) y = x.view(2 , 4 ) y[0 , 0 ] = 100 print (x)
逻辑描述 :包含张量的形状(shape)、步长(stride)、数据类型(dtype)、设备(device)等元信息。
不涉及内存拷贝 :修改信息区(如 view())不会改变存储区。
2. view() 操作
纯视图操作 :仅修改信息区的 shape 和 stride,不改变存储区 。
前提条件 :张量必须是内存连续 的(否则会报错)。
共享存储区 :与原张量共享同一存储区。
1 2 3 4 5 6 x = torch.arange(1 , 9 ) y = x.view(2 , 4 ) y[0 , 0 ] = 100 print (x)
不连续时的报错
1 2 3 4 5 x = torch.randn(2 , 3 ).transpose(0 , 1 ) try : y = x.view(6 ) except RuntimeError as e: print (e)
3. reshape() 操作
智能选择 :若张量连续,则行为与 view() 相同(共享存储区);若张量不连续,则自动调用 contiguous() 创建新存储区 。
无报错保证 :无论原张量是否连续,均可执行。
1 2 3 4 5 6 7 8 9 10 x = torch.arange(1 , 9 ) y = x.reshape(2 , 4 ) y[0 , 0 ] = 100 x = torch.randn(2 , 3 ).transpose(0 , 1 ) y = x.reshape(6 ) y[0 ] = 100 print (x[0 , 0 ])
unsqueeze(1)
unsqueeze(1) 用于在指定维度(这里是第1维)上增加一个长度为1的维度 ,从而扩展张量的形状(shape)。这个操作不会改变张量的数据,只是调整它的维度结构,类似于 NumPy 中的 np.expand_dims()。
一维张量 → 二维张量(列向量)**
1 2 3 4 5 6 import torchx = torch.tensor([1 , 2 , 3 ]) y = x.unsqueeze(1 ) print (y.shape) print (y)
输出 :
1 2 3 tensor([[1], [2], [3]])
解释 :原始形状 (3,) → 插入第1维后变为 (3, 1)(3行1列的矩阵)。
与 view()/reshape() 的区别
方法
作用
是否改变数据
是否要求连续性
unsqueeze
仅插入维度
❌ 否
✅ 不要求
view/reshape
重新调整形状
❌ 否
view 要求连续存储
关键区别 :unsqueeze 仅扩展维度,而 view/reshape 可以任意调整形状(但需元素总数一致)。
其他操作
Storage() 返回存储区内容
stride() 是在指定维度dim中从一个元素跳到下一个元素所必需的步长。当没有参数传入时,返回所有步长的元组。否则,将返回一个整数值作为特定维度dim中的步长。
[b][c][t] 对应物理内存的位置通过以下公式计算:
内存位置 = b * stride[0] + c * stride[1] + t * stride[2]
permute 重新排列维度顺序,但不改变内存中的数据,只是修改步长(stride)和形状(shape)。
视图操作:permute 是零拷贝操作,性能极高。
可能破坏连续性:新步长可能导致内存不连续。连续性条件:步长必须满足 stride[i] = stride[i+1] * shape[i+1]
torch.sort(x, descending=True) # 降序
torch.arange() 是 PyTorch 中用于生成一个一维张量(向量)的函数,该张量包含从起始值到结束值(不包括结束值)的均匀间隔序列。
torch.where(condition, x, y) → Tensor torch.where 是 PyTorch 中的一个条件选择函数,它根据给定的条件(布尔张量)从两个输入张量中选择元素。其功能类似于 NumPy 的 np.where 或 Python 的三元表达式 x if condition else y,但支持张量的并行化计算和 GPU 加速。
torch.zeros_like(...):生成与 weighted_down_out 形状相同的全零张量。
torch.full_like(input, fill_value, dtype=None, device=None) → Tensor 返回:与 input 形状相同的张量,所有元素均为 fill_value。
.numel(): 计算切片后形状元组中所有维度的乘积(即元素总数)。对于 (B, T),结果为 B * T。
.expand(-1, top_k) [num_tokens, 1] 沿第-1(最后)维度扩展(复制),变成 [num_tokens, top_k]
2. 创建张量 PyTorch提供了多种创建张量的方式,以下是一些常见的创建方法:
(1) 从Python列表或NumPy数组创建 1 2 3 4 5 6 7 8 9 10 11 import torchtensor_from_list = torch.tensor([1 , 2 , 3 ]) print (tensor_from_list) import numpy as npnumpy_array = np.array([4 , 5 , 6 ]) tensor_from_numpy = torch.from_numpy(numpy_array) print (tensor_from_numpy)
(2) 创建特定形状的张量 1 2 3 4 5 6 7 8 9 10 11 zeros_tensor = torch.zeros(2 , 3 ) print (zeros_tensor)ones_tensor = torch.ones(2 , 3 ) print (ones_tensor)rand_tensor = torch.rand(2 , 3 ) print (rand_tensor)
torch.rand:均匀分布 (Uniform Distribution)
torch.randn:标准正态分布 (Standard Normal Distribution)
(3) 创建特定范围的张量 1 2 3 4 5 6 7 arange_tensor = torch.arange(0 , 10 , 2 ) print (arange_tensor) linspace_tensor = torch.linspace(0 , 1 , 5 ) print (linspace_tensor)
torch.arange 是 PyTorch 中用于生成等间隔数值序列的函数。它可以接受一个、两个或三个参数,具体用法如下:
一个参数 :torch.arange(end)
生成从 0 到 end-1 的整数序列。
示例:torch.arange(5) 生成张量 [0, 1, 2, 3, 4]。
两个参数 :torch.arange(start, end)
生成从 start 到 end-1 的整数序列。
示例:torch.arange(2, 5) 生成张量 [2, 3, 4]。
三个参数 :torch.arange(start, end, step)
生成从 start 到 end-1 的序列,步长为 step。
示例:torch.arange(0, 1, 0.2) 生成张量 [0.0, 0.2, 0.4, 0.6, 0.8]。
(4) 创建与现有张量形状相同的张量 1 2 3 existing_tensor = torch.tensor([[1 , 2 ], [3 , 4 ]]) new_tensor = torch.zeros_like(existing_tensor) print (new_tensor)
3. 张量的属性 每个张量都有一些重要的属性:
形状(Shape) :张量的维度大小。
数据类型(dtype) :张量中元素的数据类型(如float32、int64等)。
设备(device) :张量存储在CPU还是GPU上。
1 2 3 4 tensor = torch.rand(2 , 3 ) print ("Shape:" , tensor.shape) print ("Data type:" , tensor.dtype) print ("Device:" , tensor.device)
4. 张量的操作 PyTorch提供了丰富的张量操作,以下是一些常见的操作:
(1) 索引与切片 1 2 3 4 tensor = torch.tensor([[1 , 2 , 3 ], [4 , 5 , 6 ]]) print (tensor[0 , 1 ]) print (tensor[:, 1 ]) X[:-1 ]
在 PyTorch 和 Python 中,切片操作使用 start:end:step 的语法,而 三个冒号 ::: 并不是标准语法 。用户可能混淆了多维度切片的分隔符(逗号 ,)与步长符号(双冒号 ::) 。以下是对切片中 :: 和多维度切片的详细解释:
一、:: 双冒号的作用:步长控制 :: 是 步长(stride) 的标记,语法为 [start:end:step],用于指定切片时元素的间隔。常见用法:
正向步长 :tensor[::2] 表示从头到尾每隔 2 个元素取一个。
1 2 x = torch.tensor([1 , 2 , 3 , 4 , 5 ]) print (x[::2 ])
反向步长 :tensor[::-1] 表示逆序切片。
多维张量中的步长 :对某个维度单独设置步长。
1 2 3 tensor = torch.arange(27 ).reshape(3 , 3 , 3 ) sliced = tensor[:, ::2 , :]
二、多维度切片的分隔符(逗号 ,) 二、多维度切片的分隔符(逗号 ,) 在多维张量中,不同维度的切片通过 逗号 , 分隔,每个维度可以独立设置 start:end:step 规则。例如:
1 2 3 4 5 tensor = torch.rand(4 , 3 , 28 , 28 ) sliced = tensor[:2 , :, ::2 , ::2 ]
逗号分隔维度 :[:2, :, ::2, ::2] 表示:
第1维度:前2个元素;
第2维度:全部元素;
第3、4维度:步长为2。
三、常见应用场景 三、常见应用场景
降采样(Downsampling) : 通过步长快速缩小特征图尺寸,常用于减少计算量。
1 2 downsampled = tensor[:, :, ::2 , ::2 ]
数据增强 :
随机裁剪时结合步长生成不同视角的切片。
1 2 3 start_h = torch.randint(0 , 5 , (1 ,)) cropped = tensor[:, :, start_h::3 , start_w::3 ]
处理序列数据 :
在时间序列中按步长提取子序列。
1 2 3 sequence = torch.arange(10 ) sub_sequences = sequence.unfold(0 , 3 , 3 )
四、注意事项
内存共享 :切片操作返回的是原张量的视图(view),修改切片会影响原数据。
维度对齐 :逗号分隔的维度数需与张量维度一致,否则会报错。
负索引与步长 :反向切片时需注意索引范围。1 2 3 x = torch.tensor([1 , 2 , 3 ]) print (x[::-2 ])
总结
:: 用于步长控制 ,如 tensor[::2];
逗号 , 分隔不同维度的切片规则 ,如 tensor[:, 1:8:2, :];
::: 三冒号不存在,可能是对多维度切片的误解。
合理使用步长和多维度切片,可以高效处理图像、序列和高维数据。
(2) 改变形状 1 2 3 tensor = torch.arange(6 ) reshaped_tensor = tensor.view(2 , 3 ) print (reshaped_tensor)
在使用 view、reshape 方法时,如果其中一个维度的大小为 -1,那么 PyTorch 会自动推断这个维度的大小。
注意:
元素总数必须匹配:使用 view 变形时,新形状的元素总数必须与原张量的元素总数相同。否则会抛出错误。
连续性要求:view 操作要求张量是连续存储的(即内存布局是连续的)。如果张量不是连续的,可以先调用 .contiguous() 方法将其变为连续的再进行变形。
(3) 数学运算 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 a = torch.tensor([1 , 2 , 3 ]) b = torch.tensor([4 , 5 , 6 ]) print (a + b) matrix_a = torch.tensor([[1 , 2 ], [3 , 4 ]]) matrix_b = torch.tensor([[5 , 6 ], [7 , 8 ]]) print (torch.matmul(matrix_a, matrix_b)) torch.index_add_ torch.cumsum(input , dim, *, dtype=None , out=None ) → Tensor
torch.einsumtorch.einsum 用于执行基于爱因斯坦求和约定(Einstein summation convention)的张量操作。爱因斯坦求和约定是一种表示张量运算的简洁方式,可以用于矩阵乘法、外积、转置等多种操作。
爱因斯坦求和约定通过使用索引标签来指定张量的维度,并通过重复索引标签来表示求和操作。具体来说:
单个索引标签 :表示一个维度。
重复的索引标签 :表示在这些维度上进行求和。
不同的索引标签 :表示新的维度。
torch.einsum(equation, *operands) 其中:
equation 是一个字符串,描述了输入张量如何组合以生成输出张量。
*operands 是输入张量。
1 2 3 4 5 6 7 8 import torch # 定义矩阵 A A = torch.tensor([[1, 2, 3], [4, 5, 6]], dtype=torch.float32) # 使用 einsum 进行矩阵转置 A_T = torch.einsum("ij->ji", A) print(A_T)
假设你有两个矩阵 A 和 B,你想计算它们的矩阵乘法 C = A @ B。
1 2 3 4 5 6 7 8 import torchA = torch.tensor([[1 , 2 ], [3 , 4 ]]) B = torch.tensor([[5 , 6 ], [7 , 8 ]]) C = torch.einsum('ik,kj->ij' , A, B) print (C)
输出:
1 2 tensor([[19, 22], [43, 50]])
解释:
ik 表示矩阵 A 的维度。
kj 表示矩阵 B 的维度。
ij 表示结果矩阵 C 的维度。
重复的索引 k 表示在该维度上进行求和。
外积(Outer Product)是一种张量运算,用于生成一个新的张量,其维度是输入张量维度的组合。具体来说,外积操作将两个向量扩展为一个矩阵,其中每个元素是两个向量对应元素的乘积。
给定两个向量 ( \mathbf{a} ) 和 ( \mathbf{b} ),它们的外积 ( \mathbf{C} ) 定义为:
[ \mathbf{C}_{ij} = \mathbf{a}_i \times \mathbf{b}_j ]
其中 ( \mathbf{a} ) 是一个 ( m )-维向量,( \mathbf{b} ) 是一个 ( n )-维向量,结果矩阵 ( \mathbf{C} ) 是一个 ( m \times n ) 的矩阵。
假设我们有两个向量:
[ \mathbf{a} = \begin{bmatrix} 1 \ 2 \ 3 \end{bmatrix} ] [ \mathbf{b} = \begin{bmatrix} 4 \ 5 \end{bmatrix} ]
它们的外积 ( \mathbf{C} ) 计算如下:
[ \mathbf{C} = \mathbf{a} \otimes \mathbf{b} = \begin{bmatrix} 1 \times 4 & 1 \times 5 \ 2 \times 4 & 2 \times 5 \ 3 \times 4 & 3 \times 5 \end{bmatrix} = \begin{bmatrix} 4 & 5 \ 8 & 10 \ 12 & 15 \end{bmatrix} ]
1 freqs = torch.einsum("i,j->ij" , t, inv_freq).to(dtype)
t 是一个一维张量(向量),假设其形状为 (m,)。
inv_freq 是另一个一维张量(向量),假设其形状为 (n,)。
"i,j->ij" 是 einsum 的方程字符串:
i 表示 t 的维度。
j 表示 inv_freq 的维度。
ij 表示结果张量 freqs 的维度。
结果 freqs 是一个形状为 (m, n) 的二维张量,其中每个元素 freqs[i, j] = t[i] * inv_freq[j]。
假设 t 和 inv_freq 的值如下:
1 2 3 4 5 6 7 import torcht = torch.tensor([1 , 2 , 3 ], dtype=torch.float32) inv_freq = torch.tensor([0.1 , 0.2 ], dtype=torch.float32) freqs = torch.einsum("i,j->ij" , t, inv_freq) print (freqs)
输出:
1 2 3 tensor([[0.1000, 0.2000], [0.2000, 0.4000], [0.3000, 0.6000]])
解释:
t 是一个形状为 (3,) 的向量。
inv_freq 是一个形状为 (2,) 的向量。
freqs 是一个形状为 (3, 2) 的矩阵,其中每个元素是 t 和 inv_freq 对应元素的乘积。
scatter_add_ scatter_add_(dim, index, src)
沿 dim=0 方向,将 src(ones)的值累加到 token_counts 的 index(filtered_experts)指定位置。 示例: filtered_experts = [1, 3, 4, 4],ones = [1, 1, 1, 1]。 执行后:
1 2 3 4 token_counts[1] += 1 → [0, 1, 0, 0, 0] token_counts[3] += 1 → [0, 1, 0, 1, 0] token_counts[4] += 1 → [0, 1, 0, 1, 1] token_counts[4] += 1 → [0, 1, 0, 1, 2]
最终 token_counts = [0, 1, 0, 1, 2]。
torch.bincount 也能实现相同功能,但:
1. 性能问题:scatter_add_ 是原位操作(in-place),通常比 bincount 更快。 2. 显存优化:避免创建临时张量,减少显存占用。
(4) 广播机制 PyTorch支持广播机制,允许对不同形状的张量进行运算:
1 2 3 a = torch.tensor([[1 , 2 , 3 ]]) b = torch.tensor([1 , 2 , 3 ]) print (a + b)
(5) 张量的拼接与分割 1 2 3 4 5 6 7 8 9 a = torch.tensor([1 , 2 , 3 ]) b = torch.tensor([4 , 5 , 6 ]) print (torch.cat((a, b), dim=0 )) tensor = torch.arange(6 ) chunks = torch.chunk(tensor, 2 ) print (chunks)
torch.cat 是 PyTorch 中用于在给定维度上拼接张量的函数。以下是 torch.cat 的使用说明和 dim 参数的解释:
1 torch.cat(tensors, dim=0 , *, out=None ) -> Tensor
- `tensors`: 需要拼接的张量序列,可以是元组或列表。
- `dim`: 指定在哪个维度上进行拼接,默认值为 0。`dim=-1` 表示最后一个维度。`dim=-2` 表示倒数第二个维度,依此类推。
示例 一维张量拼接
1 2 3 4 5 6 import torcha = torch.tensor([1 , 2 , 3 ]) b = torch.tensor([4 , 5 , 6 ]) result = torch.cat((a, b), dim=0 ) print (result)
二维张量拼接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 a = torch.tensor([[1 , 2 ], [3 , 4 ]]) b = torch.tensor([[5 , 6 ], [7 , 8 ]]) result_0 = torch.cat((a, b), dim=0 ) print (result_0)result_1 = torch.cat((a, b), dim=1 ) print (result_1)
通过这些示例可以看出,`dim` 参数决定了拼接的方向和方式。
5. 张量与GPU PyTorch支持将张量移动到GPU上进行加速计算:
1 2 3 4 5 6 7 8 9 10 if torch.cuda.is_available(): device = torch.device("cuda" ) else : device = torch.device("cpu" ) tensor = torch.tensor([1 , 2 , 3 ]) tensor = tensor.to(device) print (tensor.device)
自动求导机制(Autograd) PyTorch 的自动求导机制(Autograd)是它的一个核心功能,能够自动计算梯度,从而简化了反向传播和优化过程。它通过 torch.autograd 模块来实现,主要用于自动计算张量的梯度,并执行链式法则(即,反向传播)。
在 PyTorch 中,In-place 操作指的是直接修改张量内容的操作,而不是创建一个新张量。例如,tensor.add_()、tensor.mul_() 等函数就是典型的 in-place 操作。In-place 操作有一个潜在的问题:它们会修改原始数据,这可能会影响到计算图,从而导致梯度计算出现问题。
在训练过程中,PyTorch 会构建一个计算图,其中包含了所有的操作和依赖关系。在进行反向传播(backward)时,PyTorch 会依照计算图计算梯度。如果你对某个张量进行了 in-place 操作,可能会破坏计算图的结构,导致反向传播失败。
因此,PyTorch 会检查你是否在计算图中正在被使用的 Variable 上执行了 in-place 操作。如果发生这种情况,PyTorch 会在反向传播时抛出错误。这确保了,如果没有错误,反向传播中的梯度计算一定是正确的。
举个例子,如果你有一个张量 x,并对它执行了 x.add_(5)(in-place 操作),然后在反向传播时 PyTorch 会发现这个张量已经被修改,导致错误。所以 PyTorch 会提醒你避免这种情况发生。
基本概念
张量(Tensor) :
PyTorch 中的数据类型叫做 Tensor,它可以在计算图中存储数据,并支持梯度计算。
当一个张量需要计算梯度时,requires_grad 参数必须设置为 True。
1 2 3 import torchx = torch.ones(2 , 2 , requires_grad=True ) print (x)
计算图(Computation Graph) :
在 PyTorch 中,所有的操作(加法、乘法、矩阵乘法等)都会构成一个计算图,这个图记录了张量之间的运算关系。
只有 requires_grad=True 的张量及其操作会被记录在计算图中 ;
非 requires_grad=True 的张量(如默认的 requires_grad=False)不会被追踪,它们的计算不影响计算图和梯度的计算。
每个操作都会被记录为一个节点,边表示张量之间的关系。
当我们执行反向传播时,PyTorch 会根据这个计算图逐步计算梯度。
梯度(Gradients) :
梯度是用来指导模型学习(模型参数通过梯度+优化器更新)的,通常在训练过程中通过反向传播来计算。
PyTorch 的 autograd 会自动追踪每个张量的操作,并计算梯度。
主要功能和操作 1. requires_grad 参数
当你创建一个张量时,如果你想计算它的梯度,必须设置 requires_grad=True, 否则默认值是 False 。
如果 requires_grad 为 True,PyTorch 会追踪该张量的所有操作。
1 2 3 x = torch.randn(2 , 2 , requires_grad=True ) y = x * 2 z = y.mean()
在这个例子中,x 被标记为需要计算梯度,因此 y 和 z 也会被自动记录到计算图中。
固定张量 是不需要计算梯度的,比如不变的输入数据、计算中常量的、模型中的常量参数。
需要迭代更新的张量 是需要计算梯度的,一般是模型的可训练参数(如 nn.Linear 层的 weight 和 bias)。可以通过优化器和自动计算的梯度来更新这些张量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 optimizer = optim.SGD(model.parameters(), lr=0.01 ) x = torch.randn(1 , 2 ) y_true = torch.randn(1 , 1 ) y_pred = model(x) loss = criterion(y_pred, y_true) loss.backward() optimizer.step()
2. backward() 方法 当计算图建立完毕并且需要进行反向传播时,调用 backward() 方法,它会计算梯度并存储在对应张量的 .grad 属性中。
1 2 z.backward() print (x.grad)
这个 backward() 方法会计算 z 相对于 x 的梯度,并将结果存储在 x.grad 中。你可以通过 x.grad 访问梯度。
3. 禁用梯度计算(torch.no_grad()) 在某些情况下,我们可能不希望追踪梯度,比如在模型推理(inference)时。可以通过 torch.no_grad() 上下文管理器来禁用梯度计算。
1 2 with torch.no_grad(): y = x * 2
这样,y 的计算不会记录到计算图中,节省了内存和计算。
当你不需要某个张量参与梯度计算时,可以使用 .detach() 方法将该张量从计算图中分离出来,这样它就不会影响到后续的梯度计算。
detach() 方法返回一个新的张量,这个张量和原来的张量共享数据,但不会跟踪历史操作,也不会被用于计算梯度。
举个例子:
1 2 3 x = torch.tensor([1.0 , 2.0 ], requires_grad=True ) y = x * 2 z = y.detach()
当你不再需要计算梯度时,可以使用 detach() 将不需要的部分从计算图中“脱离”,例如在某些特定的推理任务中,你可能只需要计算某些张量的值,而不希望它们参与梯度计算。
这样做的好处是节省内存和计算资源,尤其在训练和推理分离的情况下非常常用。
4. retain_graph 参数 默认情况下,反向传播计算完成后,计算图会被销毁。但如果你需要多次反向传播(例如,在多次计算中反向传播),可以使用 retain_graph=True 来保留计算图。
1 z.backward(retain_graph=True )
5. grad_fn 属性 每个张量都包含一个 grad_fn 属性,记录了产生该张量的操作。如果你对一个张量进行了操作,PyTorch 会自动为该操作创建一个节点,并将它赋值给 grad_fn。
1 2 print (x.grad_fn) print (y.grad_fn)
6. 多张量反向传播 在某些情况下,可能有多个输出需要计算梯度,可以通过传递一个梯度输入来指定反向传播的起点。
1 y.backward(torch.ones_like(y))
下面是一个简单的例子,展示了如何使用 autograd:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import torchx = torch.ones(2 , 2 , requires_grad=True ) y = x * 2 z = y.mean() z.backward() print (x.grad)
这段代码中,x 是一个 2x2 的张量,经过乘法操作后,y 是一个包含 x * 2 的新张量,最后计算了 y 的平均值 z。调用 z.backward() 会计算 z 相对于 x 的梯度,结果存储在 x.grad 中。
复杂组件的自动求导
1 2 3 4 5 6 7 8 9 import torchimport torch.nn as nnlinear = nn.Linear(2 , 1 ) print (linear.weight.requires_grad) print (linear.bias.requires_grad)
在构建神经网络时会使用 nn.Module 类及其子类(如 nn.Linear),但是自动微分的机制在这些高层组件背后依然起作用。
torch.nn.Module 是所有神经网络层(例如 Linear、Conv2d 等)和模型的基类。每个 nn.Module 组件都有内部的张量(如权重和偏置),并且会执行一些计算。即使是这些高层组件,自动微分机制依然会工作,跟我们直接操作 Tensor 时的机制是相同的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import torchimport torch.nn as nnimport torch.optim as optimclass SimpleModel (nn.Module): def __init__ (self ): super (SimpleModel, self ).__init__() self .linear = nn.Linear(2 , 1 ) def forward (self, x ): return self .linear(x) model = SimpleModel() criterion = nn.MSELoss() optimizer = optim.SGD(model.parameters(), lr=0.01 ) x = torch.randn(1 , 2 ) y_true = torch.randn(1 , 1 ) y_pred = model(x) loss = criterion(y_pred, y_true) loss.backward() print (model.linear.weight.grad) print (model.linear.bias.grad) optimizer.step()
可训练参数 识别 一般来说,
神经网络层(如 nn.Linear、nn.Conv2d、nn.BatchNorm2d 等) 通常会有可训练参数,
而像激活函数(sigmoid、ReLU 等)和归一化层(nn.MaxPool2d、nn.AvgPool2d 等)中并不是所有的操作都有可训练参数。
可以通过以下几种方法来判断一个层或模块是否有可训练参数:
每个 PyTorch nn.Module(例如 nn.Linear、nn.Conv2d 等)都有一个 parameters() 方法,它返回该模块的所有可训练参数。
如果该模块有可训练的参数(即 requires_grad=True),这些参数就会出现在 parameters() 中。
示例代码 “
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import torchimport torch.nn as nnlinear_layer = nn.Linear(2 , 1 ) conv_layer = nn.Conv2d(1 , 1 , 3 ) sigmoid_layer = nn.Sigmoid() print ("Linear layer parameters:" )for param in linear_layer.parameters(): print (param.shape) print ("\nConv2d layer parameters:" )for param in conv_layer.parameters(): print (param.shape) print ("\nSigmoid layer parameters:" )for param in sigmoid_layer.parameters(): print (param.shape)
输出 :
1 2 3 4 5 6 7 8 9 Linear layer parameters: torch.Size([1, 2]) # weight torch.Size([1]) # bias Conv2d layer parameters: torch.Size([1, 1, 3, 3]) # weight torch.Size([1]) # bias Sigmoid layer parameters:
对于 nn.Linear 和 nn.Conv2d 层,parameters() 方法返回了 权重(weight) 和 偏置(bias) ,这些都是可训练参数。
对于 nn.Sigmoid,它没有任何可训练参数,parameters() 返回的是空的。
named_parameters() 方法类似于 parameters(),但它会返回每个参数的名称,帮助你更清晰地看到每个参数的作用。
1 2 3 4 print ("Linear layer named parameters:" )for name, param in linear_layer.named_parameters(): print (f"{name} : {param.shape} " )
输出 :
1 2 3 Linear layer named parameters: weight: torch.Size([1, 2]) bias: torch.Size([1])
这样,你可以更清楚地看到每个参数的名称(例如 weight 和 bias)。
如果你只是想检查某个参数是否是可训练的,可以直接查看它的 requires_grad 属性。
1 2 3 for param in linear_layer.parameters(): print (param.requires_grad)
自动注册参数 PyTorch 的 nn.Module 会自动管理你定义的层的可训练参数(如 weight 和 bias),这是通过 Python 中的 torch.nn.Parameter 类型实现的。
torch.nn.Parameter
**torch.nn.Parameter**:当你定义模型的参数时(比如权重 weight 或偏置 bias),如果你将它们定义为 nn.Parameter 对象,它们会自动注册为该模块的可训练参数。PyTorch 会把这些 Parameter 自动放入模块的 parameters() 返回的列表中。
**nn.Module 会追踪这些 Parameter**:一旦你将这些 Parameter 注册到 nn.Module 中,nn.Module 会自动管理它们的 requires_grad 属性,并将它们添加到模块的参数集合中。
1 2 3 4 5 6 7 8 9 10 11 12 class Linear (nn.Module): def __init__ (self, in_features, out_features, bias=True ): super (Linear, self ).__init__() self .weight = Parameter(torch.Tensor(out_features, in_features)) if bias: self .bias = Parameter(torch.Tensor(out_features)) else : self .bias = None self .reset_parameters()
在这个代码中:
self.weight 和 self.bias 都是 nn.Parameter 对象,因此它们会被自动注册为该 Linear 层的可训练参数。
nn.Module 会通过调用 self.parameters() 或 self.named_parameters() 来收集这些 Parameter 对象。
注册流程
当你实例化 一个层(例如 nn.Linear)时,PyTorch 会自动为该层的权重和偏置(如果存在)创建 nn.Parameter 对象,并将它们添加到模块的参数列表中。
这样,所有继承自 nn.Module 的层都会自动将它们的可训练参数注册到父类的 parameters() 方法中。
这段代码实际上不会按你预期的方式注册层的参数。让我们来分析一下为什么:
1 2 3 4 5 6 7 8 class MyModel (nn.Module): def __init__ (self ): super (MyModel, self ).__init__() def forward (self, x ): tmp1 = nn.Linear(2 , 2 ) tmp2 = nn.Conv2d(1 , 1 , 3 ) return tmp2(tmp1(x))
在 forward 方法中,你在每次执行前向传播时,创建了 tmp1 和 tmp2 两个层(nn.Linear 和 nn.Conv2d)。但是问题是:
tmp1 和 tmp2 是局部变量 :它们仅在 forward 方法内部存在,无法像类属性那样在类实例中持久保存。每次调用 forward 时,都会重新创建这些层的实例。
即使梯度被跟踪了,优化器也更新了参数,但是下次forward执行时也用不了,会重新创建新的参数。
管理 PyTorch 会通过内部机制来管理层的参数。具体来说:
当你调用 parameters() 或 named_parameters() 时,nn.Module 会返回它管理的所有 nn.Parameter 对象。
这些 Parameter 对象的 requires_grad 属性会自动设置为 True,除非你显式地设置为 False。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class MyModel (nn.Module): def __init__ (self ): super (MyModel, self ).__init__() self .layer1 = nn.Linear(2 , 2 ) self .layer2 = nn.Conv2d(1 , 1 , 3 ) def forward (self, x ): return self .layer2(self .layer1(x)) model = MyModel() for param in model.parameters(): print (param.shape)
PyTorch 的实现并不会把所有层的参数直接放入一个 parameters() 列表,而是通过一种递归方式来查找所有子模块的参数。每当你在模型中创建一个子模块(如 nn.Linear、nn.Conv2d 等),PyTorch 会自动注册该模块的 Parameter 对象,并将这些对象添加到父模块的参数集合中。
具体来说,nn.Module 类有一个 _modules 属性,这个属性用来存储该模块的所有子模块(例如 self.layer1 和 self.layer2)。在调用 parameters() 时,nn.Module 会递归地查找所有子模块,并返回所有 Parameter 对象。